﻿namespace UIAStudio
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Runtime.CompilerServices;
    using System.Threading;

    public class WorkQueue : IDisposable
    {
        private bool _blocked;
        private List<WorkItem> _highPriQueue;
        private ThreadStart _initialization;
        private List<WorkItem> _lowPriQueue;
        private static TimeSpan _maximumWorkItemTime = new TimeSpan(0, 0, 1);
        private ManualResetEvent _resetEvent;
        private Timer _resilianceTimer;
        private bool _shutdown;
        private TimeSpan _timeSpan;
        private Thread _workerThread;

        public event EventHandler Blocked;

        public event EventHandler Unblocked;

        public event EventHandler WorkCompleted;

        public WorkQueue()
        {
            this._highPriQueue = new List<WorkItem>();
            this._lowPriQueue = new List<WorkItem>();
            this._resetEvent = new ManualResetEvent(false);
            this._workerThread = new Thread(new ThreadStart(this.ThreadProcedure));
            this._workerThread.SetApartmentState(ApartmentState.STA);
            this._workerThread.Start();
            this._timeSpan = new TimeSpan(0, 0, 10);
        }

        public WorkQueue(ThreadStart initialization) : this()
        {
            Validate.ArgumentNotNull(initialization, "initialization");
            this._initialization = initialization;
        }

        public void AbortCurrentWorkItem()
        {
            this._workerThread.Abort();
            this._workerThread = new Thread(new ThreadStart(this.ThreadProcedure));
            this._workerThread.Start();
        }

        public void Dispose()
        {
            this.Dispose(true);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                this._resilianceTimer.Dispose();
                this._shutdown = true;
                this._resetEvent.Set();
                this._workerThread.Join(this._timeSpan);
                this._resetEvent.Close();
            }
            this._resilianceTimer = null;
            this._resetEvent = null;
            this._workerThread = null;
        }

        public void Enqueue(WorkItem item)
        {
            this.Enqueue(item, WorkItemPriority.High);
        }

        public void Enqueue(WorkItem item, WorkItemPriority priority)
        {
            List<WorkItem> targetQueue;
            Validate.ArgumentNotNull(item, "item");
            if (priority == WorkItemPriority.High)
            {
                targetQueue = this._highPriQueue;
            }
            else
            {
                if (priority != WorkItemPriority.Low)
                {
                    throw new ArgumentException("No known WorkItemPriority", "priority");
                }
                targetQueue = this._lowPriQueue;
            }
            lock (targetQueue)
            {
                targetQueue.Add(item);
            }
            this._resetEvent.Set();
        }

        private void Execute(WorkItem workItem)
        {
            Validate.ArgumentNotNull(workItem, "workItem");
            try
            {
                this._blocked = false;
                this._resilianceTimer.Change((int) _maximumWorkItemTime.TotalMilliseconds, -1);
                workItem.Execute();
            }
            catch (Win32Exception ex)
            {
                Trace.WriteLine("Exception! " + ex.ToString());
            }
            catch (Exception ex)
            {
                Trace.WriteLine("Exception! " + ex.ToString());
            }
            finally
            {
                this._resilianceTimer.Change(-1, -1);
                if (this._blocked)
                {
                    this._blocked = false;
                    this.OnUnblocked(null);
                }
            }
        }

        protected virtual void OnBlocked(EventArgs e)
        {
            if (this.Blocked != null)
            {
                this.Blocked(this, e);
            }
        }

        protected virtual void OnUnblocked(EventArgs e)
        {
            if (this.Unblocked != null)
            {
                this.Unblocked(this, e);
            }
        }

        protected virtual void OnWorkComplete(EventArgs e)
        {
            if (this.WorkCompleted != null)
            {
                this.WorkCompleted(this, e);
            }
        }

        private void ThreadProcedure()
        {
            bool signalled = false;
            this._resilianceTimer = new Timer(new TimerCallback(this.WorkItemTimeout), null, -1, -1);
            if (this._initialization != null)
            {
                this._initialization();
            }
            while (!this._shutdown)
            {
                WorkItem item;
                while ((this._highPriQueue.Count != 0) && !this._shutdown)
                {
                    lock (this._highPriQueue)
                    {
                        item = this._highPriQueue[0];
                        this._highPriQueue.RemoveAt(0);
                    }
                    this._resetEvent.Reset();
                    this.Execute(item);
                }
                if ((this._lowPriQueue.Count != 0) && !this._shutdown)
                {
                    lock (this._lowPriQueue)
                    {
                        item = this._lowPriQueue[0];
                        this._lowPriQueue.RemoveAt(0);
                    }
                    this.Execute(item);
                }
                else
                {
                    if (signalled)
                    {
                        this.OnWorkComplete(null);
                    }
                    signalled = this._resetEvent.WaitOne(this._timeSpan, false);
                }
            }
        }

        private void WorkItemTimeout(object status)
        {
            this._blocked = true;
            this.OnBlocked(null);
        }
    }
}

